/*
 * Copyright (c) 2012 by Paweł Lebioda <pawel.lebioda89@gmail.com>
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <getopt.h>
#include <libgen.h>

#include "random.h"
#include "debug.h"

const char * appname;
int ignore_err=0;
int verbose = 0;
#define VALUE_NAME_LEN		10

#define SEPARATOR()		printf("----------------------------------------------------------\n")

struct option opts[] = {
{"verbose",		no_argument,		NULL, 'v'},
{"help",		no_argument,		NULL, 'h'},
{"ignore-err",		no_argument,		NULL, 'e'},
{"err",			no_argument,		NULL, 'E'},
{"read",		required_argument,	NULL, 'R'},
{"get-size",		no_argument, 		NULL, 's'},
{"set-size",		required_argument,	NULL, 'S'},
{"get-min",		no_argument,		NULL, 'n'},
{"set-min",		required_argument,	NULL, 'N'},
{"get-max",		no_argument,		NULL, 'x'},
{"set-max",		required_argument,	NULL, 'X'},
{"get-core",		no_argument,		NULL, 'c'},
{"set-core",		required_argument,	NULL, 'C'},
{"get-mode",		no_argument,		NULL, 'm'},
{"set-mode",		required_argument,	NULL, 'M'},
{"mode-global",		no_argument,		NULL, 'g'},
{"mode-local",		no_argument,		NULL, 'l'},
{"reset",		no_argument,		NULL, 'r'},
{"get-type",		no_argument,		NULL, 't'},
{"set-type",		required_argument,	NULL, 'T'},
{"get-interval",	no_argument,		NULL, 'i'},
{"set-interval",	required_argument,	NULL, 'I'},
};

void help(void)
{
printf("\
Usage: %s [OPERATIONS] DEVICE\
\n",appname);
printf("\n\
OPERATIONS:\
\n");
printf("\
--help,		-h		Display this help\
\n");
printf("\
--verbose,	-v		Verbose\
\n");
printf("\
--ignore-err,	-e		Ignore errors\
\n");
printf("\
--err,		-E		Do not ignore errors [Default]\
\n");
printf("\
--read,		-R	INT	Read number of bytes\
\n");
printf("\
--get-size,	-s		Get size of data in bytes produced by random [int(4), short(2), char(1)]\
\n");
printf("\
--set-size,	-S	INT	Set size of data in bytes produced by random [int(4), short(2), char(1)]\
\n");
printf("\
--get-min,	-n		Get minimum value produced by random\
\n");
printf("\
--set-min,	-N	int	Set minimum value produced by random\
\n");
printf("\
--get-max,	-x		Get maximum value produced by random\
\n");
printf("\
--set-max,	-X	int	Set maximum value produced by random\
\n");
printf("\
--get-core,	-c		Get actual value of core of generator\
\n");
printf("\
--set-core,	-C	int	Set actual value of core of generator\
\n");
printf("\
--get-mode,	-m		Get mode of generator [global(%d),local(%d)]\
\n",RANDOM_MODE_GLOBAL,RANDOM_MODE_LOCAL);
printf("\
--set-mode,	-M	int	Set mode of generator [global(%d),local(%d)]\
\n",RANDOM_MODE_GLOBAL,RANDOM_MODE_LOCAL);
printf("\
--mode-local,	-l		Set mode to local\
\n");
printf("\
--mode-global,	-g		Set mode to global\
\n");
printf("\
--reset,	-r		Reset generator to defaults\
\n");
printf("\
--get_type,	-t		Get type: size,min,max\
\n");
printf("\
--set-type,	-T int,int,int	Set type: size,min,max\
\n");
printf("\
--get-interval,	-i		Get interval: min,max\
\n");
printf("\
--set-interval, -I     int,int	Set interval: min,max\
\n");
}

void print_interval(struct random_interval * interval)
{
	printf(">>> %-*s: %d\n",VALUE_NAME_LEN,"MIN",interval->min);
	printf(">>> %-*s: %d\n",VALUE_NAME_LEN,"MAX",interval->max);

}

void print_type(struct random_type * type)
{
	printf(">>> %-*s: %d\n",VALUE_NAME_LEN,"SIZE",type->size);
	print_interval(&type->interval);
}

int get_int(const char * str)
{
	int val;
	val = atoi(str);
	if(val==0 && str[0] != '0') {
		fprintf(stderr,RED"%s: invalid int value\n"ENDCOLOR,str);
		exit(EXIT_FAILURE);
	}
	return val;
}

int get_type(char * arg,struct random_type * type)
{
	int ret = sscanf(arg,"%d,%d,%d",&type->size,&type->interval.min,&type->interval.max);
	if(ret != 3) return -1;
	return 0;
}

int get_interval(char * arg,struct random_interval * interval)
{
	int ret = sscanf(arg,"%d,%d",&interval->min,&interval->max);
	if(ret != 2) return -1;
	return 0;
}

void ioctl_cmd(int fd,int cmd,const char * name)
{
	int ret;
	printf(GREEN "CMD" BLUE " %-*s" ENDCOLOR ": ",VALUE_NAME_LEN,name);
	ret = ioctl(fd,cmd);
	printf("[ret: %d]\n",ret);
	if(ret<0) {
		perror(name);
		if(!ignore_err) exit(EXIT_FAILURE);
	}
}

void ioctl_set_value(int fd,int cmd,const char * name,int val)
{
	int ret;
	printf(YELLOW "SET" BLUE " %-*s" ENDCOLOR ": ",VALUE_NAME_LEN,name);
	ret = ioctl(fd,cmd,&val);
	printf("[ret: %d]",ret);
	if(ret<0) {
		printf("\n");
		perror(name);
		if(!ignore_err) exit(EXIT_FAILURE);
	} else {
		printf(">%s = %d\n",name,val);
	} 
}
void ioctl_get_value(int fd,int cmd,const char * name)
{
	int ret,tmp;
	printf(BLUE "GET" YELLOW " %-*s" ENDCOLOR ": ",VALUE_NAME_LEN,name);
	ret = ioctl(fd,cmd,&tmp);
	printf("[ret: %d]",ret);
	if(ret<0) {
		printf("\n");
		perror(name);
		if(!ignore_err) exit(EXIT_FAILURE);
	} else {
		printf(">%s = %d\n",name,tmp);
	}
	
}

void ioctl_get_type(int fd)
{
	int ret;
	struct random_type type;
	printf(BLUE "GET" YELLOW " %-*s" ENDCOLOR ": ",VALUE_NAME_LEN,"TYPE");
	ret = ioctl(fd,RANDOM_IOC_GET_TYPE,&type);
	printf("[ret: %d]\n",ret);
	if(ret<0) {
		perror("TYPE");
		if(!ignore_err) exit(EXIT_FAILURE);
	} else {
		print_type(&type);
	}
}

void ioctl_set_type(int fd,char * arg)
{
	int ret;
	struct random_type type;
	if(get_type(arg,&type)) {
		fprintf(stderr,"%s: invalid type format (size,min,max)\n",arg);
		exit(EXIT_FAILURE);
	}	
	printf(YELLOW "SET" BLUE " %-*s" ENDCOLOR ": (%s) ",VALUE_NAME_LEN,"TYPE",arg);
	ret = ioctl(fd,RANDOM_IOC_SET_TYPE,&type);
	printf("[ret: %d]\n",ret);
	if(ret<0) {
		perror("TYPE");
		if(!ignore_err) exit(EXIT_FAILURE);
	} else {
		print_type(&type);
	}

}

void ioctl_get_interval(int fd)
{
	int ret;
	struct random_interval interval;
	printf(BLUE "GET" YELLOW " %-*s" ENDCOLOR ": ",VALUE_NAME_LEN,"INTERVAL");
	ret = ioctl(fd,RANDOM_IOC_GET_INTERVAL,&interval);
	printf("[ret: %d]\n",ret);
	if(ret<0) {
		perror("INTERVAL");
		if(!ignore_err) exit(EXIT_FAILURE);
	} else {
		print_interval(&interval);
	}

}

void ioctl_set_interval(int fd,char * arg)
{
	int ret;
	struct random_interval interval;
	if(get_interval(arg,&interval)) {
		fprintf(stderr,"%s: invalid interval format (min,max)\n",arg);
		exit(EXIT_FAILURE);
	}	
	printf(YELLOW "SET" BLUE " %-*s" ENDCOLOR ": (%s) ",VALUE_NAME_LEN,"INTERVAL",arg);
	ret = ioctl(fd,RANDOM_IOC_SET_INTERVAL,&interval);
	printf("[ret: %d]\n",ret);
	if(ret<0) {
		perror("INTERVAL");
		if(!ignore_err) exit(EXIT_FAILURE);
	} else {
		print_interval(&interval);
	}

}

void read_random(int fd,int count)
{
	struct random_type type;
	int ret,mode,core,i;
	unsigned char * buff;
	buff = malloc(count);
	if(!buff) {
		perror("malloc");
		if(ignore_err) return;
		exit(EXIT_FAILURE);
	}
	printf(BLUE "READ %*s" ENDCOLOR ": %d byte%c\n",VALUE_NAME_LEN-1,"",count,(count>1)?'s':' ');
	if((ret = ioctl(fd,RANDOM_IOC_GET_TYPE,&type))<0) {
		perror("RANDOM_IOC_GET_TYPE");
		if(ignore_err) return;
		exit(EXIT_FAILURE);
	}
	if((ret = ioctl(fd,RANDOM_IOC_GET_MODE,&mode))<0) {
		perror("RANDOM_IOC_GET_MODE");
		if(ignore_err) return;
		exit(EXIT_FAILURE);
	}
	if((ret = ioctl(fd,RANDOM_IOC_GET_CORE,&core))<0) {
		perror("RANDOM_IOC_GET_CORE");
		if(ignore_err) return;
		exit(EXIT_FAILURE);
	}
	SEPARATOR();
	printf(">>> %-*s: %s(%d)\n",VALUE_NAME_LEN,"MODE",(mode==RANDOM_MODE_GLOBAL)?"global":((mode==RANDOM_MODE_LOCAL)?"local":"unknown"),mode);
	print_type(&type);
	printf(">>> %-*s: %d\n",VALUE_NAME_LEN,"CORE",core);
	SEPARATOR();
	ret = read(fd,buff,count);
	if(ret<0) {
		perror("read");
		if(ignore_err) return;
		exit(EXIT_FAILURE);
	}
	if(ret != count) {
		printf(RED"WARNING!: "ENDCOLOR"only %d byte%c has been read\n",ret,(ret>1)?'s':' ');
	}
	for(i=0;i<ret;i++) {
		if(i%16 == 0) {
			printf("%s%08x: ",(i==0)?"":"\n",i);
		} else if(i%8 == 0){
			printf(" ");	
		} 
		printf("%02x ",buff[i]);
	}	
	printf("\n");
	SEPARATOR();
	free(buff);
}

int main(int argc,char * argv[]) 
{
		int fd,opt,long_index,val;
		const char * devname;
		appname = basename(argv[0]);
		if(argc == 1) {
			help();
			exit(EXIT_FAILURE);			
		}	
		devname = argv[argc-1];
		if(!strcmp(devname,"--help") || !strcmp(devname,"help") || !strcmp(devname,"-h")) {
			help();
			exit(EXIT_SUCCESS);			

		}	
		fd = open(devname,O_RDWR);
		if(fd<0) {
			perror(devname);
			exit(EXIT_FAILURE);
		}
		while((opt=getopt_long(argc,argv,"heER:sS:nN:xX:cC:mM:glrtT:iI:",opts,&long_index))!=-1) {
			switch(opt) {
				case 'h':
					help();
					break;
				case 'v':	
					verbose = 1;
					break;
				case 'e':
					ignore_err = 1;
					break;
				case 'E':
					ignore_err = 0;
					break;
				case 'R':
					val = get_int(optarg);
					if(val<=0) {
						fprintf(stderr,"%d: value must be grater than zero\n",val);
						exit(EXIT_FAILURE);
					}	
					read_random(fd,val);
					break;	
				case 's':
					ioctl_get_value(fd,RANDOM_IOC_GET_SIZE,"SIZE");
					break;
				case 'S':
					val = get_int(optarg);
					ioctl_set_value(fd,RANDOM_IOC_SET_SIZE,"SIZE",val);
					break;
				case 'n':
					ioctl_get_value(fd,RANDOM_IOC_GET_MIN,"MIN");
					break;
				case 'N':
					val = get_int(optarg);
					ioctl_set_value(fd,RANDOM_IOC_SET_MIN,"MIN",val);
					break;
				case 'x':
					ioctl_get_value(fd,RANDOM_IOC_GET_MAX,"MAX");
					break;
				case 'X':
					val = get_int(optarg);
					ioctl_set_value(fd,RANDOM_IOC_SET_MAX,"MAX",val);
					break;
				case 'c':
					ioctl_get_value(fd,RANDOM_IOC_GET_CORE,"CORE");
					break;
				case 'C':
					val = get_int(optarg);
					ioctl_set_value(fd,RANDOM_IOC_SET_CORE,"CORE",val);
					break;
				case 'm':
					ioctl_get_value(fd,RANDOM_IOC_GET_MODE,"MODE");
					break;
				case 'M':
					val = get_int(optarg);
					ioctl_set_value(fd,RANDOM_IOC_SET_MODE,"MODE",val);
					break;
				case 'g':
					ioctl_cmd(fd,RANDOM_IOC_MODE_GLOBAL,"MODE-GLOBAL");
					break;
				case 'l':
					ioctl_cmd(fd,RANDOM_IOC_MODE_LOCAL,"MODE-LOCAL");
					break;
				case 'r':
					ioctl_cmd(fd,RANDOM_IOC_RESET,"RESET");				
					break;
				case 't':
					ioctl_get_type(fd);
					break;
				case 'T':
					ioctl_set_type(fd,optarg);
					break;
				case 'i':
					ioctl_get_interval(fd);
					break;
				case 'I':
					ioctl_set_interval(fd,optarg);
					break;
				case '?':
					break;
				default:
					fprintf(stderr,"getopt_long returned character code '%c'\n",(char)opt);
					break;
			}
		}
		close(fd);
}
